//
//  StringCategories.swift
//  SwiftKit
//
//  Created by LL on 2023/4/6.
//

import Foundation

public extension String {
    /// 获取路径扩展名(`如果有的话`)。
    ///
    /// 下表说明了该方法对不同路径的影响：
    /// | 原字符串 | 目标字符串 |
    /// | :-: | :-: |
    /// | "/tmp/scratch.tiff" | "tiff" |
    /// | ".scratch.tiff" | "tiff" |
    /// | "/tmp/scratch" | "" |
    /// | "/tmp/scratch." | "" |
    /// | "/tmp/scratch.framework/scrach" | "" |
    /// | "/tmp/" | "" |
    /// | "/tmp/scratch..tiff" | "tiff" |
    var pathExtension: String {
        if self.isEmpty { return self }
        
        /*
         文件夹名有可能包含「.」符号，例如："/tmp/scratch.framework/FBSDKCoreKit"
         所有需要先获取路径最后一个组件。
         */
        let lastPathComponent = self.lastPathComponent
        let endIndex = lastPathComponent.lastIndex(of: ".")
        guard let endIndex = endIndex else { return "" }
        
        let nextIndex = lastPathComponent.index(after: endIndex)
        let pathExtension = lastPathComponent[nextIndex...]
        return pathExtension.trimmingCharacters(in: .whitespacesAndNewlines)
    }
    
    
    /// 删除原路径的扩展名并返回一个新路径。
    ///
    /// 下表说明了该方法对不同路径的影响:
    /// | 原字符串 | 目标字符串 |
    /// | :-: | :-: |
    /// | "scratch.tiff" | "scratch" |
    /// | "scratch" | "scratch" |
    /// | "/tmp/scratch.tiff" | "/tmp/scratch" |
    /// | "scratch.bundle/" | "scratch" |
    /// | "scratch..tiff" | "scratch." |
    /// | ".tiff" | "" |
    /// | "/tmp/abc.framework/file" | "/tmp/abc.framework/file" |
    var deletingPathExtension: String {
        if self.isEmpty { return self }
        
        /*
         文件夹名有可能包含「.」符号，例如："/tmp/scratch.framework/FBSDKCoreKit"
         所有需要先获取路径最后一个组件。
         */
        let lastPathComponent = self.lastPathComponent
        if !lastPathComponent.contains(".") { return self }
        
        if let endIndex = self.lastIndex(of: ".") {
            return String(self[..<endIndex])
        } else {
            return self
        }
    }
    
    
    /// 获取路径最后一个组件。
    ///
    /// 下表说明了该方法对不同路径的影响：
    /// | 原字符串 | 目标字符串 |
    /// | --- | --- |
    /// | "/tmp/scratch.tiff" | "scratch.tiff" |
    /// | "/tmp/scratch" | "scratch" |
    /// | "/tmp/" | "tmp" |
    /// | "scratch///" | "scratch" |
    /// | "/" | "/" |
    var lastPathComponent: String {
        if self.isEmpty || self == "/" { return self }
        
        let newStr = self.trimmingSuffixString("/")
        let endIndex = newStr.lastIndex(of: "/")
        guard let endIndex = endIndex else { return newStr }
        
        let nextIndex = newStr.index(after: endIndex)
        return String(newStr[nextIndex...])
    }
    
    
    /// 删除原路径最后一个组件并返回一个新路径。
    ///
    /// 下表说明了该方法对不同路径的影响：
    /// | 原字符串 | 目标字符串 |
    /// | --- | --- |
    /// | "/tmp/scratch.tiff" | "/tmp" |
    /// | "/tmp/lock/" | "/tmp" |
    /// | "/tmp/" | "/" |
    /// | "/tmp" | "/" |
    /// | "/" | "/" |
    /// | "/tmp///" | "/" |
    var deletingLastPathComponent: String {
        if self.isEmpty || self == "/" { return self }
        
        let newStr = self.trimmingSuffixString("/")
        let endIndex = newStr.lastIndex(of: "/")
        guard let endIndex = endIndex else { return newStr }
        if endIndex == newStr.startIndex { return "/" }
        
        return String(newStr[..<endIndex])
    }
    
    
    /// 获取字符串最后一个字符的索引，如果字符串为空则返回nil。
    var endCharIndex: String.Index? {
        if self.isEmpty { return nil }
        return self.index(before: self.endIndex)
    }
    
    
    /// 如果路径是文件夹则返回 true。
    var isFolder: Bool {
        if self.isEmpty { return false }
        
        var isDirectory = ObjCBool(false)
        FileManager.default.fileExists(atPath: self, isDirectory: &isDirectory)
        
        return isDirectory.boolValue
    }
    
    
    /// 如果路径是文件则返回 true。
    var isFile: Bool {
        if self.isEmpty { return false }
        
        var isDirectory = ObjCBool(false)
        if FileManager.default.fileExists(atPath: self, isDirectory: &isDirectory), !isDirectory.boolValue {
            return true
        }
        
        return false
    }
    
    
    /// 如果路径存在则返回true。
    var isExist: Bool {FileManager.default.fileExists(atPath: self)}
    
    
    /// 在原路径末尾添加一个路径组件并返回一个新路径。
    ///
    /// 下表说明了该方法对不同路径的效果：
    /// | 原字符串 | suffix | 目标字符串 |
    /// | --- | --- | --- |
    /// | "/tmp" | "scratch.tiff" | "/tmp/scratch.tiff" |
    /// | "/tmp/" | "scratch.tiff" | "/tmp/scratch.tiff" |
    /// | "/tmp///" | "scratch.tiff" | "/tmp/scratch.tiff" |
    /// | "/tmp///" | "//scratch.tiff" | "/tmp/scratch.tiff" |
    /// | "/" | "scratch.tiff" | "/scratch.tiff" |
    /// | "" | "scratch.tiff" | "scratch.tiff" |
    func appendingPathComponent(_ suffix: String) -> String {
        self.appendingPathComponent(suffix, "")
    }
    
    
    /// 在原路径末尾添加多个路径组件并返回一个新路径。
    ///
    /// 效果请参考 `appendingPathComponent(:String)` 函数。
    ///
    /// ```
    /// "/user/".appendingPathComponent("abc/", "/file.txt") => "/user/abc/file.txt"
    /// ```
    func appendingPathComponent(_ suffixs: String...) -> String {
        let _suffixs = suffixs.filter({!$0.isEmpty})
        if _suffixs.isEmpty { return self }
        if self.isEmpty { return _suffixs.joined(separator: "/") }
        
        var path = self.trimmingSuffixString("/")
        
        for suffix in suffixs.filter({!$0.isEmpty}) {
            path += "/" + suffix.trimmingPrefixString("/")
        }
        
        return path
    }
    
    
    /// 从原字符串的首尾删除指定字符串并返回一个新字符串。
    ///
    /// 下表说明了该方法在不同情况下的效果：
    /// | 原字符串 | string | 目标字符串 |
    /// | --- | --- | --- |
    /// | "/tmp/" | "/" | "tmp" |
    /// | "tmp/" | "/" | "tmp" |
    /// | "/t/m//p/" | "/" | "t/m//p" |
    /// | "//tm//p//" | "/" | "tm//p" |
    /// | "/" | "/" | "" |
    /// | "\@endtmp\@end" | "\@end" | "tmp" |
    func trimmingString(_ string: String) -> String {
        if self.isEmpty || string.isEmpty  { return self }
        return self.trimmingCharacters(in: CharacterSet(charactersIn: string))
    }
    
    
    /// 从原字符串的开始处删除指定字符串并返回一个新字符串。
    ///
    /// 下表说明了该方法在不同情况下的效果：
    /// | 原字符串 | string | 目标字符串 |
    /// | --- | --- | --- |
    /// | "/tmp/" | "/" | "tmp/" |
    /// | "///tmp/" | "/" | "tmp/" |
    /// | "///tmp/" | "@" | "///tmp/" |
    /// | "endendtmp/" | "end" | "tmp/" |
    func trimmingPrefixString(_ string: String) -> String {
        if self.isEmpty || string.isEmpty  { return self }
        
        var newStr = self
        while newStr.hasPrefix(string) {
            let index = newStr.index(newStr.startIndex, offsetBy: string.count)
            newStr.removeSubrange(..<index)
        }
        
        return newStr
    }
    
    
    /// 从原字符串的末尾处删除指定字符串并返回一个新字符串。
    ///
    /// 下表说明了该方法在不同情况下的效果：
    /// | 原字符串 | string | 目标字符串 |
    /// | --- | --- | --- |
    /// | "/tmp/" | "/" | "/tmp" |
    /// | "/tmp///" | "/" | "/tmp" |
    /// | "///tmp/" | "@" | "///tmp/" |
    /// | "tmpendend" | "end" | "tmp" |
    func trimmingSuffixString(_ string: String) -> String {
        if self.isEmpty || string.isEmpty  { return self }
        
        var newStr = self
        while newStr.hasSuffix(string) {
            let index = newStr.index(newStr.endIndex, offsetBy: -string.count)
            newStr.removeSubrange(index...)
        }
        
        return newStr
    }
    
    
    /// 返回指定值在字符串中逆序第 `number` 次出现的索引。
    ///
    /// 下表说明了该方法在不同情况下的效果，假设原字符串是 "/Suf/fixStri/ng/"，ch是 "/"：
    /// | number | 索引位置 |
    /// | --- | --- |
    /// | 1 | 下标15 |
    /// | 2 | 下标12 |
    /// | 3 | 下标4 |
    /// | 4 | 下标0 |
    /// | 5 | nil |
    func lastIndex(of ch: Character, number: Int) -> String.Index? {
        if number < 1 { return nil }
        if number == 1 { return self.lastIndex(of: ch) }
        
        var num = 0
        let index = self.lastIndex { letter in
            if letter == ch { num += 1 }
            return num == number
        }
        
        return index
    }
    
    
    /// 返回指定值在字符串中正序第 `number` 次出现的索引。
    ///
    /// 下表说明了该方法在不同情况下的效果，假设原字符串是 "/Suf/fixStri/ng/"，ch是 "/"：
    /// | number | 索引位置 |
    /// | --- | --- |
    /// | 1 | 下标0 |
    /// | 2 | 下标4 |
    /// | 3 | 下标12 |
    /// | 4 | 下标15 |
    /// | 5 | nil |
    func firstIndex(of ch: Character, number: Int) -> String.Index? {
        if number < 1 { return nil }
        if number == 1 { return self.firstIndex(of: ch) }
        
        var num = 0
        let index = self.firstIndex { letter in
            if letter == ch { num += 1 }
            return num == number
        }
        
        return index
    }
}
